﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ttlang.node;

//拼字符串模组
namespace ttlang.builder
{
    public enum BuildMode
    {
        System,
        CommonJS,
    }
    public class TsBuilderContext
    {
        public bool withhtml
        {
            get;
            private set;
        }
        public BuildMode mode
        {
            get;
            private set;
        }
        
        public static TsBuilderContext Parse(ProjectOption.ProjectType outputType,Newtonsoft.Json.Linq.JObject buildOption)
        {
           var option = new TsBuilderContext();
            option.mode = Enum.Parse<BuildMode>(buildOption["mode"].ToString(), true);
            if (outputType == ProjectOption.ProjectType.EXE)
            {
                option.withhtml = (bool)buildOption["withhtml"];
            }
            else
            {
                option.withhtml = false;
            }
            return option;
        }

        public semantic.TTMethod entrypoint;
    }
    //为啥是cplus，我们产生的代码风格是以c为基础，cpp为辅助。
    //所以只取一个plus
    public class builder_ts : IBuilder
    {
        public string Name => "CPlus SourceCode";
        public BuildTarget type => BuildTarget.TypeScript;
        public Result BuildNode(Node node, out string result)
        {
            if (node is Node_Expr expr)
            {
                StringBuilder sb = new StringBuilder();
                BuildNode_Expr(sb, expr);
                result = sb.ToString();
                return null;
            }
            else if (node is Node_CodeAtom code)
            {
                StringBuilder sb = new StringBuilder();
                BuildNode_Code(sb, code, 0);
                result = sb.ToString();
                return null;
            }

            throw new Exception("not support node:" + node.type);
        }


        //支持System 和CommonJS两种build形式
        //将来细化

        //是否产生测试html，目前仅对应system+exe模式的
        

        public Result BuildModule(semantic.Module module, string outputpath, string temppath, ProjectOption.ProjectType outputType, bool autoBuild, Newtonsoft.Json.Linq.JObject buildOption)
        {
            TsBuilderContext option;
            if (buildOption!=null)
            {
                option = TsBuilderContext.Parse(outputType, buildOption);
            }
            else
            {
                throw new Exception("must use targetoption for build typescript");
                //"targetoption": {
                //    "mode": "system",//将来会支持导出 commonjs 模式 和 system模式的代码
                //  "withhtml": true
                //},
            }
            Result result = new Result(outputpath,temppath);
            result.target = BuildTarget.TypeScript;

            ExportNameSpace(option,result, "", module._defNameSpace);
            
            //目前导出固定为 system module,不要导出module ,system 不是这个用法
            
            //cs.ProjectExporter.ExportTSProjSystem(outputpath);
            ProjectExporter.ExportTSProjFile(option, outputpath, outputType);
          
            if (option.withhtml)
            {
                ProjectExporter.ExportTSProjHtml(outputpath);
            }
            if (autoBuild)
            {
                AutoBuild.BuildTSProj(outputpath);
                result.autobuild = System.IO.Path.Combine(outputpath, "bin/main.js");
            }

            return result;
        }
        public void ExportNameSpace(TsBuilderContext option,Result result, string name, semantic.TTNameSpace _namespace)
        {
            var newname = string.IsNullOrEmpty(name) ? _namespace.name : (name + "_" + _namespace.name);

            if ((_namespace.methods != null && _namespace.methods.Count > 0)
                ||
                (_namespace.vars != null && _namespace.vars.Count > 0)
                )
            {
                var membername = newname;
                if (string.IsNullOrEmpty(membername))
                    membername = "__global";
                ExportTSNameSpaceMember(option,result, membername, _namespace);
            }
            if (_namespace.namespaces != null)
            {
                foreach (var n in _namespace.namespaces.Values)
                {
                    ExportNameSpace(option,result, newname, n);
                }
            }
            if (_namespace.types != null)
            {
                foreach (var t in _namespace.types.Values)
                {
                    FileExporter.ExportType(option,result, newname, t);
                }
            }
        }

        public static void ExportTSNameSpaceMember(TsBuilderContext option, Result result, string name, semantic.TTNameSpace _namespace)
        {
            StringBuilder coder = new StringBuilder();
            //coder.AppendLine("using System;");
            //c# 不允许namespace有member，全部用class套起来
            // ts export 是导出为一个模块，其他类或者模块需要引用当前模块时，只需要 import导入即可
            coder.AppendLine("namespace " + name);
            coder.AppendLine("{");
            if (_namespace.methods != null)
            {
                foreach (var m in _namespace.methods.Values)
                {
                    BuildMethod(option,coder, 1, m);
                }
            }
            if (_namespace.vars != null)
            {
                foreach (var v in _namespace.vars.Values)
                {
                    BuildVar(option,coder, 1, v);
                }
            }
            coder.AppendLine("}");

            if(option.entrypoint!=null)
            {
                coder.AppendLine("//add entrypoint call code.");
                coder.AppendLine(option.entrypoint.fullname + "();");
                option.entrypoint = null;
            }
            var filename = name + ".ts";
            var len = coder.Length;

            result.WriteFile(filename, coder.ToString());
            result.outputFiles[filename] = len;
        }

        public static void BuildMethod(TsBuilderContext option, StringBuilder coder, int deep, semantic.TTMethod method)
        {

            bool isMain = false;
            if (method.attributes != null)//入口点检查
            {
                for (var i = 0; i < method.attributes.Length; i++)
                {
                    var _m = method.attributes[i].function as Node_Expr_Method;
                    if (_m.method.name == "EntryPoint")
                    {
                        isMain = true;
                    }
                }
            }
            string space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";
            coder.Append(space);

            //將來加上各種modifer以後，逻辑应该是有entrypoint的必须是static函数，现在还没区分static
            //所有namespace里面的也是static函数
            if (isMain)
            {
                coder.Append("export function ");
                if (option.entrypoint != null)
                    throw new Exception("have more than one entrypoint.");
                option.entrypoint = method;
            }
            //coder.Append(cs.TypeConverter.ConvertTSType(method.ReturnType.fullname));
            coder.Append( method.name + "(");
            for (var i = 0; i < method.args.Length; i++)
            {
                coder.Append(ts.TypeConverter.ConvertType(method.args[i].argtype.fullname) + " " + method.args[i].name);
                if (i != method.args.Length - 1)
                    coder.Append(",");
            }
            coder.AppendLine(")");


            if (method.body != null)
            {
               BuildNode_Code(coder, method.body, deep);
            }
            else
            {
                coder.AppendLine(space + "{");
                coder.AppendLine(space + "}");
            }
        }
        public static void BuildVar(TsBuilderContext option, StringBuilder coder, int space, semantic.TTVar method)
        {

        }

        public static void BuildNode_Expr(StringBuilder sb, Node_Expr expr)
        {
            switch (expr.type)
            {
                case NodeType.EXPR_CONTAINER:
                    {
                        var v = expr as Node_Expr_Container;
                        //根据传入的数据类型，替换成ts中对应的类型
                        sb.Append(ts.TypeConverter.ConvertType(v.container.fullname));
                    }
                    break;
                case NodeType.EXPR_NUMBER:
                    {
                        var v = expr as Node_Expr_Number;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append(v.srcnumber);
                    }
                    break;
                case NodeType.EXPR_STRING:
                    {
                        var v = expr as Node_Expr_String;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append(v.value);

                    }
                    break;

                case NodeType.EXPR_MATH:
                    {
                        var v = expr as Node_Expr_Math;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append("(");

                        //这里暂时简单处理，实际build的时候可能会出现数学表达式变函数调用的情况
                        BuildNode_Expr(sb, v.left);
                        sb.Append(" " + v.token + " ");
                        BuildNode_Expr(sb, v.right);

                        sb.Append(")");

                    }
                    break;
                case NodeType.EXPR_TEMPVAR:
                    {
                        var v = expr as Node_Expr_TempVar;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append(v.id.id);
                    }
                    break;
                case NodeType.EXPR_METHOD:
                    {
                        var v = expr as Node_Expr_Method;
                        if (v == null)
                            throw new Exception("error type");
                        //这也是个简单的处理，以后要考虑method的namespace
                        sb.Append(v.method.name);
                    }
                    break;
                case NodeType.EXPR_3OP:
                    {

                        var v = expr as Node_Expr_Math3Op;
                        if (v == null)
                            throw new Exception("error type");

                        sb.Append("(");

                        BuildNode_Expr(sb, v.condition);

                        sb.Append("?");

                        BuildNode_Expr(sb, v.vTrue);

                        sb.Append(":");

                        BuildNode_Expr(sb, v.vFalse);


                        sb.Append(")");
                    }
                    break;
                case NodeType.EXPR_MEMBER:
                    {
                        var v = expr as Node_Expr_Member;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Expr(sb, v.left);
                        sb.Append(".");
                        var str= consoleType(v.member.name);
                        sb.Append(str);
                    }
                    break;

                case NodeType.EXPR_CALL:
                    {
                        var v = expr as Node_Expr_Call;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Expr(sb, v.function);
                        sb.Append("(");
                        for (var i = 0; i < v.args?.Count; i++)
                        {
                            BuildNode_Expr(sb, v.args[i]);
                            if (i < v.args.Count - 1)
                                sb.Append(",");
                        }
                        sb.Append(")");
                    }
                    break;
                case NodeType.BOOL:
                    {
                        var v = expr as Node_Expr_Bool;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append(v.value);

                    }
                    break;
                default:
                    throw new NotImplementedException();

            }

        }
       public static void BuildNode_Code(StringBuilder sb, Node_CodeAtom code, int deep, bool skiplineend = false)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";
            switch (code.type)
            {
                //代码块
                case NodeType.CODE_BLOCK:
                    {
                        var block = code as Node_CodeBlock;
                        if (block == null)
                            throw new Exception("error type");
                        sb.AppendLine(space + "{");
                        for (var i = 0; i < block.items.Count; i++)
                        {
                            BuildNode_Code(sb, block.items[i], deep + 1);
                        }
                        sb.AppendLine(space + "}");
                    }
                    break;
                    //注释
                case NodeType.CODE_COMMENT:
                    {
                        var v = code as Node_Code_Comment;
                        if (v == null)
                            throw new Exception("error type");
                        sb.Append(space + v.comment);
                        if (!skiplineend || v.commonttype == CommentType.SINGLELINE)
                            sb.AppendLine();
                        break;
                    }
                case NodeType.CODE_VALUEDEF:
                    {
                        var v = code as Node_Code_ValueDef;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Code_ValueDef(sb, v, deep, skiplineend);
                        break;
                    }
                case NodeType.CODE_SET:
                    {
                        var v = code as Node_Code_Set;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Code_Set(sb, v, deep, skiplineend);
                        break;
                    }
                case NodeType.CODE_SELFOP:
                    {
                        var v = code as Node_Code_SelfOf;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Code_SelfOp(sb, v, deep, skiplineend);
                        break;
                    }
                case NodeType.CODE_FOR:
                    {
                        var v = code as Node_Code_For;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Code_For(sb, v, deep, skiplineend);
                        break;
                    }
                case NodeType.CODE_CALL:
                    {
                        var v = code as Node_Code_Call;
                        if (v == null)
                            throw new Exception("error type");
                        BuildNode_Code_Call(sb, v, deep, skiplineend);
                        break;
                    }
                case NodeType.CODE_RETURN:
                    {
                        var v = code as Node_Code_Return;
                        sb.Append(space + "return");
                        if (v.vlaue != null)
                        {
                            sb.Append(" ");
                            BuildNode_Expr(sb, v.vlaue);
                        }
                        sb.AppendLine(";");
                    }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }
        static void BuildNode_Code_ValueDef(StringBuilder sb, Node_Code_ValueDef code, int deep, bool skiplineend)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";
            var _type = ts.TypeConverter.ConvertType(code.deftype.fullname);
            if (_type == "number")
            {
                sb.Append(space + "let " + code.id);
            }
            else
            {
                //code.initvlaue.isConst
                //sb.Append(space +"let "+ code.id + ":" + code.deftype.fullname);
                sb.Append(space + "let " + code.id + ":" + _type);
            }
            //sb.Append(space + code.deftype.fullname + " " + code.id);
            if (code.initvlaue != null)
            {
                sb.Append(" = ");
                BuildNode_Expr(sb, code.initvlaue);
            }
            if (!skiplineend)
                sb.AppendLine(";");

        }
        static void BuildNode_Code_SelfOp(StringBuilder sb, Node_Code_SelfOf code, int deep, bool skiplineend)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";
            {
                sb.Append(space);
                BuildNode_Expr(sb, code.id_expr);
            }
            sb.Append(" " + code.token + " ");
            if (code.rightvalue != null)
            {
                BuildNode_Expr(sb, code.rightvalue);
            }
            if (!skiplineend)
                sb.AppendLine(";");
        }
        static void BuildNode_Code_Set(StringBuilder sb, Node_Code_Set code, int deep, bool skiplineend)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";
            sb.Append(space + code.id);

            sb.Append(" = ");
            BuildNode_Expr(sb, code.initvlaue);

            if (!skiplineend)
                sb.AppendLine(";");

        }
        static void BuildNode_Code_For(StringBuilder sb, Node_Code_For code, int deep, bool skiplineend)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";

            sb.Append(space + "for(");
            if (code.elem_init != null)
                BuildNode_Code(sb, code.elem_init, 0, true);
            sb.Append(";");
            if (code.elem_cond != null)
                BuildNode_Expr(sb, code.elem_cond);
            sb.Append(";");
            if (code.elem_step != null)
                BuildNode_Code(sb, code.elem_step, 0, true);
            sb.AppendLine(")");

            //body
            BuildNode_Code(sb, code.body, deep, false);
        }
        static void BuildNode_Code_Call(StringBuilder sb, Node_Code_Call code, int deep, bool skiplineend)
        {
            var space = "";
            for (var i = 0; i < deep; i++)
                space += "    ";

            sb.Append(space);
            BuildNode_Expr(sb, code.call_expr);
            if (!skiplineend)
                sb.AppendLine(";");
        }
        //ts 目前简单粗暴的大小写转换
        static string consoleType(string consoletype)
        {
            if (consoletype == "Warn")
            {
                consoletype = "warn";
            }
            else if (consoletype == "Log")
            {
                consoletype = "log";
            }
            else if (consoletype == "Error") {
                consoletype = "error";
            } 
            else if (consoletype=="ToString") {
                consoletype = "toString";
            }
            return consoletype;
        }
    }
}
